/* License Notice:
**
** This program is free software: you can redistribute it and/or modify
**    it under the terms of the GNU General Public License as published by
**    the Free Software Foundation, either version 3 of the License, or
**    (at your option) any later version.
** This program is distributed in the hope that it will be useful,
**   but WITHOUT ANY WARRANTY; without even the implied warranty of
**   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
**   GNU General Public License for more details.
** You should have received a copy of the GNU General Public License
**   along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

/**
 * @file fifo_simple.cpp
 * @author TooOld2Rock'nRoll
 * @date dec/2004
 * @see fifo_simple.cpp
 */

/*---- Includes ----*/
#include "toolbox/fifo_simple.hpp"
#include "toolbox/debug.h"

#ifdef UNIT_TESTING
#include <CppUTest/TestHarness.h>
#endif

/*---- Method Definitions ----*/
/**
 * @brief Start a new empty Queue.
 */
template <typename T>
FIFO<T>::FIFO ()
{
    _debug (TRACE, "%s ()", __FUNCTION__);
}//End Construct


/**
 * @brief Delete all nodes and free all data in the Queue.
 * @warning If template is a pointer, remember to pop all nodes to deal with the allocated memory (if any)...
 *
 * Same as clean(), but prints a benchmark report at the end when
 *   compiled with ENABLE_BENCHMARK flag.
 */
template <typename T>
FIFO<T>::~FIFO ()
{
    _debug (TRACE, "%s ()", __FUNCTION__);

    this->clean ();
}//End Destructor


/**
 * @brief Push a new package to the tail of the Queue.
 *
 * @param package - reference to the data to be saved.
 */
template <typename T>
void FIFO<T>::push (const T &package)
{
    //New node to put in this
    FIFO::node_s *new_node = nullptr;

    _debug (TRACE, "%s ()", __FUNCTION__);

    //Save information passed to function
    new_node = new FIFO::node_s ();
    new_node->_package = package;

    if (this->_tail)
        this->_tail->_next = new_node; //add new node to the queue
    else
        this->_head = new_node;

    this->_tail = new_node; //move queue tail
    ++this->_size; //Increase queue Size;
}//End push ()


/**
 * @brief Removes the package at the queue head.
 *
 * @param buffer - a buffer to save the package carried by the node.
 *
 * @return Pointer to the buffer or null if the queue is empty.
 */
template <typename T>
T* FIFO<T>::pop (T &buffer)
{
    FIFO::node_s *aux = nullptr;

    _debug (TRACE, "%s ()", __FUNCTION__);

    //check parameters
    if (!this->_head)
        return nullptr;

    //save node package and free node
    aux = this->_head;
    buffer = this->_head->_package;

    //reposition head
    this->_head = this->_head->_next;
    delete (aux);
    --this->_size;

    if (!this->_head)
        this->_tail = nullptr;

    return &buffer;
}//End pop ()


/**
 * @brief Peeks the package at the queue head.
 *
 * @return Pointer to the package under the queue head, null if the queue is empty.
 */
template <typename T>
const T* FIFO<T>::head () const
{
    _debug (TRACE, "%s ()", __FUNCTION__);

    //check parameters
    if (!this->_head)
        return nullptr;

    return &this->_head->_package;
}//End head ()


/**
 * @brief Remove all nodes from the queue.
 * @warning If template is a pointer, calling this method may generate memory leaks!
 */
template <typename T>
void FIFO<T>::clean ()
{
    FIFO::node_s *aux_node = nullptr;

    _debug (TRACE, "%s ()", __FUNCTION__);

    //check parameters
    if (!this->_head)
        return;

    do {
        //remove the first node from the queue
        aux_node = this->_head;
        this->_head = this->_head->_next;

        //free the resources
        delete (aux_node);

        //we could just size=0 after removing everything, BUT!
        //this way we are sure to keep the loop and the count in sync and any issues would be evident.
        --this->_size;
    } while (this->_head);

    this->_tail = nullptr;
}//End clean ()


#ifdef UNIT_TESTING
static int x = 0,
           buff = 0;
static FIFO<int> *queue = nullptr;

TEST_GROUP(fifo_s_cpp)
{
    void setup ()
    {
        queue = new FIFO<int> ();
    }//end setup ()

    void teardown ()
    {
        delete (queue);
        queue = nullptr;

        x = 0;
        buff = 0;
    }//end teardown ()
};//end TEST_GROUP(fifo_s_cpp)

TEST(fifo_s_cpp, check_empty_read)
{
    CHECK(queue->head () == nullptr);
}

TEST(fifo_s_cpp, push_and_check_head)
{
    x = rand ();
    queue->push (x);

    CHECK_EQUAL(x, *queue->head ());
};

TEST(fifo_s_cpp, push_two_and_check_head)
{
    x = rand ();
    queue->push (x);

    queue->push (rand ());

    CHECK_EQUAL(x, *queue->head ());
};//end TEST(fifo_s_cpp, push_two_and_check_head)

TEST(fifo_s_cpp, pop_on_empty_queue)
{
    CHECK(queue->pop (buff) == nullptr);
};

TEST(fifo_s_cpp, push_once_pop_once)
{
    x = rand ();
    queue->push (x);

    CHECK(queue->pop (buff) != nullptr);
    CHECK_EQUAL(x, buff);
};

TEST(fifo_s_cpp, push_once_pop_twice_check_nullptr)
{
    x = rand ();
    queue->push (x);

    CHECK(queue->pop (buff) != nullptr);
    CHECK(queue->pop (buff) == nullptr);
};

TEST(fifo_s_cpp, push_twice_pop_twice_check_order)
{
    int first = rand ();
    queue->push (first);

    x = rand ();
    queue->push (x);

    CHECK(queue->pop (buff) != nullptr);
    CHECK_EQUAL(first, buff);
    buff = 0;

    CHECK(queue->pop (buff) != nullptr);
    CHECK_EQUAL(x, buff);
};//end TEST(fifo_s_cpp, push_twice_pop_twice_check_order)

TEST(fifo_s_cpp, push_twice_pop_once_check_top)
{
    int first = rand ();
    queue->push (first);

    x = rand ();
    queue->push (x);

    queue->pop (buff);

    CHECK_EQUAL(x, *queue->head ());
};//end TEST(fifo_s_cpp, push_twice_pop_once_check_top)

TEST(fifo_s_cpp, push_N_and_check_clean)
{
    int i = 0;

    for (i=10; i--; )
        queue->push (rand ());

    queue->clean ();

    CHECK(queue->head () == nullptr);
    UNSIGNED_LONGS_EQUAL(queue->size (), 0);
};//end TEST(fifo_s_cpp, push_N_and_check_clean)

TEST(fifo_s_cpp, check_empty_true)
{
    CHECK(queue->empty());
};//end TEST(fifo_s_cpp, check_empty_true)

TEST(fifo_s_cpp, check_empty_false)
{
    queue->push (rand ());

    CHECK_FALSE(queue->empty ());
};//end TEST(fifo_s_cpp, check_empty_false)

TEST(fifo_s_cpp, push_N_check_size)
{
    int i = 0;

    for (i=10; i--; )
        queue->push (rand ());

    UNSIGNED_LONGS_EQUAL(queue->size (), 10);
};//end TEST(fifo_s_cpp, push_check_size)

TEST(fifo_s_cpp, push_N_pop_check_size)
{
    int i = 0;

    for (i=10; i--; )
        queue->push (rand ());

    for (i=8; i--; )
        queue->pop (buff );

    UNSIGNED_LONGS_EQUAL(queue->size (), 2);
};//end TEST(fifo_s_cpp, push_pop_check_size)

TEST(fifo_s_cpp, push_N_clean_push_N_sanity_check)
{
    int i = 0;

    for (i=10; i--; )
        queue->push (rand ());

    queue->clean ();

    x = rand ();
    queue->push (x);
    for (i=9; i--; )
        queue->push (rand ());

    UNSIGNED_LONGS_EQUAL(queue->size (), 10);
    CHECK_EQUAL(x, *queue->head ());
}//end TEST(fifo_s_cpp, push_N_clean_push_N_sanity_check)

#endif //UNIT_TESTING



/*---- Forward Declarations ----*/
//This is necessary since we are packing a source file with templates into a static linked library!
#include "event_manager.hpp"
template class FIFO<EventManager::event_s *>;

